﻿// Autore: Alessio Parma

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace RecipesManager.Data.Implementation
{
    sealed partial class ShoppingList : IShoppingList
    {
        #region IShoppingList Members

        public int Count
        {
            get { return IngredientQuantities.Count; }
        }

        public IIngredientQuantity this[string ingredientName, string measureName]
        {
            get { return GetIngredientQuantity(ingredientName, measureName); }
        }

        public IIngredientQuantity Add(string ingredientName, string measureName, double quantity, string notes)
        {
            var iq = IngredientListQuantity.New(Id, ingredientName, measureName, quantity, notes);
            var oiq = GetIngredientQuantity(ingredientName, iq.MeasureId);
            if (oiq != null) oiq.Quantity += iq.Quantity;
            else IngredientQuantities.Add(iq);
            Manager.SaveChanges();
            return oiq ?? iq;
        }

        public void Clear()
        {
            IngredientQuantities.Clear();
            Manager.SaveChanges();
        }

        public bool Contains(string ingredientName, string measureName)
        {
            return GetIngredientQuantity(ingredientName, measureName) != null;
        }

        public IEnumerator<IIngredientQuantity> GetEnumerator()
        {
            return IngredientQuantities.OrderBy(iq => iq.Ingredient.Name).ThenBy(iq => iq.Measure.Name).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void Remove(string ingredientName, string measureName)
        {
            IngredientQuantities.Remove(GetIngredientQuantity(ingredientName, measureName));
            Manager.SaveChanges();
        }

        #endregion

        public static ShoppingList New(int dbVersion)
        {
            return CreateShoppingList(0, dbVersion);
        }

        IngredientListQuantity GetIngredientQuantity(string ingredientName, int measureId)
        {
            var iName = ingredientName.ToKey();
            return IngredientQuantities.FirstOrDefault(iq => iq.Ingredient.Name == iName && iq.Measure.Id == measureId);
        }

        IngredientListQuantity GetIngredientQuantity(string ingredientName, string measureName)
        {
            var iName = ingredientName.ToKey();
            var mName = measureName.ToKey();
            return IngredientQuantities.FirstOrDefault(iq => iq.Ingredient.Name == iName && iq.Measure.Name == mName);
        }
    }

    sealed partial class IngredientListQuantity : IIngredientQuantity
    {
        #region IIngredientQuantity Members

        string IIngredientQuantity.FormattedQuantity
        {
            get
            {
                var quantity = Quantity;
                string measure;
                switch (Measure.Name)
                {
                    case "Grammi":
                        FormattedWeightQuantity(ref quantity, out measure);
                        break;
                    case "Litri":
                        FormattedVolumeQuantity(ref quantity, out measure);
                        break;
                    case "Unità":
                        quantity = Math.Ceiling(quantity);
                        measure = "Unità";
                        break;
                    default:
                        throw new Exception("Unità di misura non valida!");
                }
                return string.Format("{0} {1}", quantity, measure);
            }
        }

        int IEntity.Id
        {
            get { return IngredientId; }
        }

        IIngredient IIngredientQuantity.Ingredient
        {
            get { return Ingredient; }
        }

        IMeasure IIngredientQuantity.Measure
        {
            get { return Measure; }
        }

        string INamedEntity.Name
        {
            get { return Ingredient.Name; }
        }

        string IIngredientQuantity.Notes
        {
            get { return Notes; }
        }

        double IIngredientQuantity.Quantity
        {
            get { return Quantity; }
        }

        public void Update(string measureName, double quantity, string notes)
        {
            Measure = (Measure) Manager.Measures[measureName];
            Quantity = quantity;
            Notes = notes;
            Manager.SaveChanges();
        }

        #endregion

        public static IngredientListQuantity New(int shoppingListId, string ingredientName, string measureName,
                                                 double quantity, string notes)
        {
            var ingredientId = Manager.Ingredients[ingredientName].Id;
            var measure = Manager.Measures[measureName];
            ConvertToStandard(ref measure, ref quantity);
            return CreateIngredientListQuantity(shoppingListId, ingredientId, measure.Id, quantity, notes);
        }

        static void ConvertToStandard(ref IMeasure currMeasure, ref double quantity)
        {
            quantity = currMeasure.ConversionFactor*quantity;
            currMeasure = (currMeasure.Type == MeasureType.Unit)
                              ? Manager.Measures["Unità"]
                              : (currMeasure.Type == MeasureType.Weight)
                                    ? Manager.Measures["Grammi"]
                                    : Manager.Measures["Litri"];
        }

        static void FormattedVolumeQuantity(ref double quantity, out string measure)
        {
            if (quantity >= 1000)
            {
                var mod = quantity%100;
                if (!mod.Equals(0)) quantity = Math.Truncate(quantity + (100 - mod));
                measure = "Litri";
            }
            else if (quantity < 1)
            {
                quantity = Math.Ceiling(quantity*1000);
                measure = "Millilitri";
            }
            else
            {
                quantity = Math.Round(quantity, 2);
                measure = "Litri";
            }
        }

        static void FormattedWeightQuantity(ref double quantity, out string measure)
        {
            if (quantity >= 1000)
            {
                var mod = quantity%100;
                if (!mod.Equals(0)) quantity = Math.Truncate(quantity + (100 - mod));
                quantity /= 1000;
                measure = "Chilogrammi";
            }
            else if (quantity < 1)
            {
                quantity = Math.Ceiling(quantity*1000);
                measure = "Milligrammi";
            }
            else
            {
                quantity = Math.Round(quantity, 2);
                measure = "Grammi";
            }
        }
    }
}